// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:buildAMD64 | | | | | | | | | | | | | | | | ios&arm64 | | mips64 | | mips64le | | ppc64 | | | | ppc64; | | | RI！ios，arm64 mips64 mips64le ppc64 ppc64le riscv64 s390x 

// 请参阅mpagealloc_32bit.go了解此处排除ios/arm64的原因。

package runtime

import "unsafe"

const (
	// 基数树中的级别数。
	summaryLevels = 5

	// 用于测试的常数。
	pageAlloc32Bit = 0
	pageAlloc64Bit = 1

	// 表示块映射L1中所有索引所需的位数。
	// 
	// 有关详细信息，请参阅（*pageAlloc）.chunks。更新文件
	// 此编号应更改。
	pallocChunksL1Bits = 13
)

// levelBits是超级摘要
// 结构中给定级别的基数位数。
// 
// levelBits的所有项之和应等于heapdRbits。
var levelBits = [summaryLevels]uint{
	summaryL0Bits,
	summaryLevelBits,
	summaryLevelBits,
	summaryLevelBits,
	summaryLevelBits,
}

// levelShift是在超级摘要结构中，为获取给定级别的基数而要移位的位数。
// 
// 使用levelShift，可以计算与
// 指针p相关的l级摘要索引，方法是：
// p>>levelShift[l]
var levelShift = [summaryLevels]uint{
	heapAddrBits - summaryL0Bits,
	heapAddrBits - summaryL0Bits - 1*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 2*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 3*summaryLevelBits,
	heapAddrBits - summaryL0Bits - 4*summaryLevelBits,
}

// levelLogPages是log2地址空间中运行时页面的最大数量
// 给定级别的摘要表示。
// 
// 叶级别始终精确表示1个区块的页面的log2。
var levelLogPages = [summaryLevels]uint{
	logPallocChunkPages + 4*summaryLevelBits,
	logPallocChunkPages + 3*summaryLevelBits,
	logPallocChunkPages + 2*summaryLevelBits,
	logPallocChunkPages + 1*summaryLevelBits,
	logPallocChunkPages,
}

// sysInit对pageAlloc中的字段
// 执行与体系结构相关的初始化。pageAlloc应该是未初始化的，除了sysStat 
// 如果应该更新任何运行时统计信息。
func (p *pageAlloc) sysInit() {
	// 为每个级别保留内存。这将由setArenas以R/W的形式映射到
	// 中。
	for l, shift := range levelShift {
		entries := 1 << (heapAddrBits - shift)

		// 在地址空间的任何位置保留b字节内存。
		b := alignUp(uintptr(entries)*pallocSumBytes, physPageSize)
		r := sysReserve(nil, b)
		if r == nil {
			throw("failed to reserve page summary memory")
		}

		// 将此保留放入一个切片中。
		sl := notInHeapSlice{(*notInHeap)(r), 0, entries}
		p.summary[l] = *(*[]pallocSum)(unsafe.Pointer(&sl))
	}
}

// sysGrow在堆上执行依赖于体系结构的操作
// 页分配器的增长，例如在新内存中映射
// 摘要。它还更新
// /中的切片长度[.summary.
// 
// base是新添加堆内存的基础，限制是
// 超过新添加堆内存末尾的第一个地址。
// 两者必须与pallochunkbytes对齐。
// 
// 调用sys后，调用方必须更新p.start和p.endGrow.
func (p *pageAlloc) sysGrow(base, limit uintptr) {
	if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
		print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
		throw("sysGrow bounds not aligned to pallocChunkBytes")
	}

	// addArrangeToSummaryRange将地址范围转换为摘要索引范围
	// ，必须映射以支持摘要范围内的地址
	// 。
	addrRangeToSummaryRange := func(level int, r addrRange) (int, int) {
		sumIdxBase, sumIdxLimit := addrsToSummaryRange(level, r.base.addr(), r.limit.addr())
		return blockAlignSummaryRange(level, sumIdxBase, sumIdxLimit)
	}

	// summaryRangeToSummaryRange转换任何
	// p.summary级别的索引范围ary转换为包含索引范围的页对齐地址。
	// ADDRANGETOSUMADDRRANGE是一个方便的函数，它将地址范围r转换为给定摘要级别的地址范围
	summaryRangeToSumAddrRange := func(level, sumIdxBase, sumIdxLimit int) addrRange {
		baseOffset := alignDown(uintptr(sumIdxBase)*pallocSumBytes, physPageSize)
		limitOffset := alignUp(uintptr(sumIdxLimit)*pallocSumBytes, physPageSize)
		base := unsafe.Pointer(&p.summary[level][0])
		return addrRange{
			offAddr{uintptr(add(base, baseOffset))},
			offAddr{uintptr(add(base, limitOffset))},
		}
	}

	// 存储r的摘要。
	addrRangeToSumAddrRange := func(level int, r addrRange) addrRange {
		sumIdxBase, sumIdxLimit := addrRangeToSummaryRange(level, r)
		return summaryRangeToSumAddrRange(level, sumIdxBase, sumIdxLimit)
	}

	// 查找首先使用严格大于base的索引。因为这个函数永远不会被要求重新映射同一个内存两次，所以这个索引实际上就是我们插入这个新增长的索引，base永远不会重叠/包含在这个新增长中
	// 
	// 这将用于查看摘要数组中已映射的内存
	// 在此新范围之前和之后映射。
	inUseIndex := p.inUse.findSucc(base)

	// 根据需要向上走基数树并映射摘要。
	for l := range p.summary {
		// 找出摘要的哪一部分数组此新地址空间需要。
		needIdxBase, needIdxLimit := addrRangeToSummaryRange(l, makeAddrRange(base, limit))

		// 使用新的上限更新摘要片。这确保了
		// 我们至少在上限上得到严格的边界检查。
		// 
		// 无论是否映射新内存，我们都必须这样做。
		if needIdxLimit > len(p.summary[l]) {
			p.summary[l] = p.summary[l][:needIdxLimit]
		}

		// 计算所需的地址级别l的摘要数组中的nge。
		need := summaryRangeToSumAddrRange(l, needIdxBase, needIdxLimit)

		// 删减需要新映射的内容。由于页面对齐要求
		// 映射的原因，它的某些部分可能会被inUse描述的内容映射。这个
		// 函数保证了Prune的不变量on永远不会被要求重新映射同一个内存两次。
		if inUseIndex > 0 {
			need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex-1]))
		}
		if inUseIndex < len(p.inUse.ranges) {
			need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex]))
		}
		// 有可能在我们删除上面的内容后，没有什么新内容需要映射。
		if need.size() == 0 {
			continue
		}

		// 映射并提交需求。
		sysMap(unsafe.Pointer(need.base.addr()), need.size(), p.sysStat)
		sysUsed(unsafe.Pointer(need.base.addr()), need.size())
	}
}
